extern "C" {
	#include <pthread.h>
	#include <unistd.h>
	#include <sys/syscall.h>
}

#include <jni.h>

namespace glf {

extern JavaVM* AndroidGetJavaVM(void);

Thread::Features Thread::GetFeatures() {
	return FEATURE_MAX_THREADS | FEATURE_MAIN_CAN_SLEEP | FEATURE_CAN_SUSPEND_RESUME;
}

#if GLF_AS_DLL
const int THREAD_PRIORITY_MIN = 19;	// Process.THREAD_PRIORITY_LOWEST;
const int THREAD_PRIORITY_NORM = 0;	// Process.THREAD_PRIORITY_DEFAULT;
const int THREAD_PRIORITY_MAX = -15;	// Real max is -20 but forcing applications not to set priority higher than AudioTrackThread
#else
const int Thread::PRIORITY_MIN = 19;	// Process.THREAD_PRIORITY_LOWEST;
const int Thread::PRIORITY_NORM = 0;	// Process.THREAD_PRIORITY_DEFAULT;
const int Thread::PRIORITY_MAX = -15;	// Real max is -20 but forcing applications not to set priority higher than AudioTrackThread
#endif

struct Thread::Impl : public thread::ImplBase {

	// Invalid thread priorities
	static const int PRIORITY_URGENT_AUDIO = -19;	// Process.THREAD_PRIORITY_URGENT_AUDIO
	static const int PRIORITY_AUDIO = -16;			// Process.THREAD_PRIORITY_AUDIO
	static const int PRIORITY_URGENT_DISPLAY = -8;	// Process.THREAD_PRIORITY_URGENT_DISPLAY
	static const int PRIORITY_DISPLAY = -4;			// Process.THREAD_PRIORITY_DISPLAY
	static const int PRIORITY_FOREGROUND = -2;		// Process.THREAD_PRIORITY_FOREGROUND

	pthread_t mPThread;
	pid_t mTId;
	int mCoreAffinityMask;
	bool mIsMain;

	// Masks for valid priorities
	unsigned int mNegativePriorityMask; // Bit i = 1 means priority '-i' is valid.
	unsigned int mPositivePriorityMask; // Bit i = 1 means priority 'i' is valid.

	Impl(Thread& thread) :
		ImplBase(thread),
		mPThread(0),
		mTId(0),
		mCoreAffinityMask(-1),
		mIsMain(false) 
	{
		CreateValidPriorityMasks();
	}

	Impl(Thread& thread, bool isMain) :
		ImplBase(thread),
		mPThread(pthread_self()),
		mTId(gettid()),
		mCoreAffinityMask(-1),
		mIsMain(isMain)
	{
		CreateValidPriorityMasks();
	}

	~Impl() {
	}

	void Join() {
		if(mPThread)
		{
			pthread_join(mPThread, 0);
			mPThread = 0;
		}
	}

	static void* RunThread(void* ud) {
		Impl* impl = static_cast<Impl*>(ud);

		impl->mTId = gettid();
		
		impl->SetCoreAffinity(impl->mCoreAffinityMask);

		Thread::MyRun(impl->mThread);

		return 0;
	}

	void SetCoreAffinity(int mask) {
		if(mTId != 0)
		{
			int syscallres = syscall(__NR_sched_setaffinity, mTId, sizeof(mask), &mask);
			if(syscallres != 0)
			{
				int err = errno;
				printf("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
			}
		}
		
		mCoreAffinityMask = mask;
	}

	int GetCoreAffinity() const {
		int mask = -1;
		int syscallres = syscall(__NR_sched_getaffinity, mTId, sizeof(mask), &mask);

		if(syscallres != 0)
		{
			int err = errno;
			printf("Error in the syscall getaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
		}
		
		return mask;
	}

	void SetPriority(int npriority) {
		JavaVM* pJavaVM = AndroidGetJavaVM();
		if(pJavaVM)
		{
			if(IsPriorityValid(npriority)) // Verify that desired priority is valid
			{
				// Get JNI pointer
				JNIEnv *env;
				(pJavaVM)->GetEnv((void**)&env, JNI_VERSION_1_2);

				// Set current thread priority
				jclass cOSProcess = env->FindClass("android/os/Process");
				jmethodID mSetPriority = env->GetStaticMethodID(cOSProcess, "setThreadPriority", "(I)V");
				env->CallStaticVoidMethod(cOSProcess, mSetPriority, npriority);

				// Shadow state
				mThread.mPriority = npriority;
			}
		}
	}

	bool Start(int priority, uint stackSize) {
		GLFi_ASSERT(mPThread == 0);

		pthread_attr_t attr;
		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
		pthread_attr_setstacksize(&attr, stackSize);

		Impl& mainThreadImpl = GetGlobals()->threadMgr.mMainThread.GetImpl();

		bool isOk = pthread_create(&mPThread, &attr, RunThread, this) == 0;

		// Clamp priority between min and max (max having the lowest numerical value)
		if(priority < Thread::PRIORITY_MAX)
		{
			priority = Thread::PRIORITY_MAX;
		}
		else if(priority > Thread::PRIORITY_MIN)
		{
			priority = Thread::PRIORITY_MIN;
		}

		// If priority is valid, set member variable value. Priority will be set from AndroidOnStartThread()
		// since thread needs to be attached before its priority can be set.
		if(IsPriorityValid(priority))
		{
			mThread.mPriority = priority;
		}

		pthread_attr_destroy(&attr);

		return isOk;
	}
	bool IsCurrent() const {
		bool isCurrent = pthread_equal(pthread_self(), mPThread) != 0;

		return isCurrent;
	}
	bool IsMain() const {
		return mIsMain;
	}

	bool IsPriorityValid(int priority){
		int priorityBit;
		unsigned int priorityMask;
		if(priority >= 0)
		{
			priorityBit = 1 << priority;
			priorityMask = mPositivePriorityMask; // priority 'i' is valid if mask bit 'i' = 1
		}
		else // Priority is negative
		{
			priorityBit = 1 << (-priority);
			priorityMask = mNegativePriorityMask; // priority '-i' is valid if mask bit 'i' = 1
		}

		return ((priorityBit & priorityMask) != 0);
	}

	void CreateValidPriorityMasks(){
		// Set all negative priorities valid from Thread::PRIORITY_MAX to 0 (priority '-i' is valid if bit 'i' = 1)
		int bit = 1;
		int maxBit = -Thread::PRIORITY_MAX;
		mNegativePriorityMask = 0;
		for(int i = 0; i <= maxBit; i++)
		{
			mNegativePriorityMask |= bit;
			bit <<= 1;
		}

		// Invalidate priority THREAD_PRIORITY_URGENT_AUDIO (used exclusively for most urgent audio threads)
		mNegativePriorityMask &= ~(1 << (-PRIORITY_URGENT_AUDIO));

		// Invalidate priority THREAD_PRIORITY_AUDIO (used exclusively for API AudioTrackThread)
		mNegativePriorityMask &= ~(1 << (-PRIORITY_AUDIO));

		// Invalidate priority THREAD_PRIORITY_DISPLAY (used exclusively for system display threads)
		mNegativePriorityMask &= ~(1 << (-PRIORITY_DISPLAY));

		// Invalidate priority THREAD_PRIORITY_URGENT_DISPLAY  (used exclusively for system display threads)
		mNegativePriorityMask &= ~(1 << (-PRIORITY_URGENT_DISPLAY));

		// Invalidate priority THREAD_PRIORITY_FOREGROUND (used exclusively for user interface threads)
		mNegativePriorityMask &= ~(1 << (-PRIORITY_FOREGROUND));

		// Set all positive priorities valid from 0 to Thread::PRIORITY_MIN (priority 'i' is valid if bit 'i' = 1)
		bit = 1;
		maxBit = Thread::PRIORITY_MIN;
		mPositivePriorityMask = 0;
		for(int i = 0; i <= maxBit; i++)
		{
			mPositivePriorityMask |= bit;
			bit <<= 1;
		}
	}

	static void Yield() {
		sched_yield();
	}
	static void Sleep(uint ms) {
		::usleep(ms*1000);
	}
};
} //namespace glf

